package com.dhcc.lock;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

/**
 * 参考ReentrantLock DistributedRedisLock
 * @author wyp
 *
 */
public class DistributedRedisLock implements Lock{
	
	private StringRedisTemplate redisTemplate;
	
	private String lockName;
	
	private String uuid;
	
	private long expire = 30;
	
	public DistributedRedisLock(String lockName,StringRedisTemplate redisTemplate) {
		this.redisTemplate  =redisTemplate;
		this.lockName = lockName;
		this.uuid = UUID.randomUUID().toString();
	}

	@Override
	public void lock() {
		// TODO Auto-generated method stub
		this.tryLock();
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean tryLock() {
		// TODO Auto-generated method stub
		try {
			return this.tryLock(-1L,TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * 枷锁方法
	 */
	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		if (time != -1){
            this.expire = unit.toSeconds(time);
        }
        // 还未上锁，已经上锁且是自己的
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
            Thread.sleep(50);
        }
        // 加锁成功，返回之前，开启定时器自动续期
        this.renewExpire();
		return true;
	}

	/**
	 * 解锁方法
	 */
	@Override
	public void unlock() {
		String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null){
            throw new IllegalMonitorStateException("this lock doesn't belong to you!");
        }
	}

	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 自动续期
	 */
	private void renewExpire() {
		String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {  // 开启的定时器是一个子线程，不能用上面的getId方法，而是直接在主线程中拼好uuid（该uuid是生成的uuid+线程数），然后这里用
                if (redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))) {
                    renewExpire();  // 再次开启一次性任务，直到上面续期失败了就直接结束了
                }  // 另一种实现方式是用周期任务，在unlock中取消定时任务
            }
        }, this.expire * 1000 / 3);// 设置成只有延迟时间，是一次性的任务，就不需要思考结束任务的方式

	}
}
